#include "wspp_fileserver.h"

#define ASIO_STANDALONE
#include <websocketpp/config/asio_no_tls.hpp>
#include <websocketpp/server.hpp>
#include <functional>
#include <set>

typedef websocketpp::server<websocketpp::config::asio> asio_server;

using websocketpp::connection_hdl;
using websocketpp::lib::placeholders::_1;
using websocketpp::lib::placeholders::_2;
using websocketpp::lib::bind;
using websocketpp::lib::ref;

typedef websocketpp::http::parser::request   ws_request;
typedef websocketpp::http::parser::response  ws_response;
typedef asio_server::message_ptr             ws_msg;
typedef websocketpp::connection_hdl          ws_connection_hdl;

template<>
const std::string & Request<ws_request>::get_method () const
{
    return request_->get_method();
}

template<>
const std::string & Request<ws_request>::get_uri () const
{
    return request_->get_uri();
}

template<>
const std::string & Request<ws_request>::get_header (const std::string &key) const
{
    return request_->get_header(key);
}

template<>
const std::string & Request<ws_request>::get_body () const
{
    return request_->get_body();
}

template<>
const std::string & Request<ws_request>::get_version () const
{
    return request_->get_version();
}

inline bool is_space_or_tab(char c) { return c == ' ' || c == '\t'; }

inline std::pair<size_t, size_t> trim(const char *b, const char *e, size_t left,
                                      size_t right) {
  while (b + left < e && is_space_or_tab(b[left])) {
    left++;
  }
  while (right > 0 && is_space_or_tab(b[right - 1])) {
    right--;
  }
  return std::make_pair(left, right);
}

inline void split(const char *b, const char *e, char d,
                  std::function<void(const char *, const char *)> fn) {
  size_t i = 0;
  size_t beg = 0;

  while (e ? (b + i < e) : (b[i] != '\0')) {
    if (b[i] == d) {
      auto r = trim(b, e, beg, i);
      if (r.first < r.second) { fn(&b[r.first], &b[r.second]); }
      beg = i + 1;
    }
    i++;
  }

  if (i) {
    auto r = trim(b, e, beg, i);
    if (r.first < r.second) { fn(&b[r.first], &b[r.second]); }
  }
}

bool parse_query_list(const std::string &s, RequestQueryParams &params) {
  std::set<std::string> cache;

  split(s.data(), s.data() + s.size(), '&', [&](const char *b, const char *e) {
    std::string kv(b, e);
    if (cache.find(kv) != cache.end()) { return; }
    cache.insert(kv);

    std::string key;
    std::string val;
    split(b, e, '=', [&](const char *b2, const char *e2) {
      if (key.empty()) {
        key.assign(b2, e2);
      } else {
        val.assign(b2, e2);
      }
    });

    if (!key.empty()) {
      params.emplace(key, val);
    }
  });
  return true;
}


template<>
void Response<ws_response>::set_status (uint32_t status_)
{
    response_->set_status((websocketpp::http::status_code::value)(status_));
}

template<>
void Response<ws_response>::set_body (const std::string &body_)
{
    response_->set_body(body_);
}

template<>
void Response<ws_response>::set_body (const uint8_t* data, uint32_t len)
{
    std::string body_;
    body_.resize(len);
    memcpy(&body_[0], data, len);
    response_->set_body(body_);
}

template<>
void Response<ws_response>::set_header(const std::string& key, const std::string& val)
{
    response_->replace_header(key, val);
}

template<>
void Response<ws_response>::append_header(const std::string& key, const std::string& val)
{
    response_->append_header(key, val);
}

template<>
uint32_t WebSocketMsg<ws_msg>::get_opcode() const
{
    return (*message_)->get_opcode();
}

template<>
const std::string& WebSocketMsg<ws_msg>::get_header ()const
{
    return (*message_)->get_header();
}

template<>
const std::string& WebSocketMsg<ws_msg>::get_payload ()const
{
    return (*message_)->get_payload();
}

template<>
void WsFileServer<ws_request, ws_response, ws_msg, ws_connection_hdl>::send_text_msg(ws_connection_hdl& con_, const std::string& msg) const
{
    //auto con_ = (connection_hdl*)con;
    auto svr = ((asio_server*)m_endpoint);
	try
	{
        svr->send(con_, msg, websocketpp::frame::opcode::text);
        //svr->interrupt(*con_);
        //svr->send(*con_, std::string("test multi msg\r\n"), websocketpp::frame::opcode::text);
	}
	catch (websocketpp::exception const &e)
	{
		std::cout << "send msg  failed because: " << "(" << e.what() << ")" << std::endl;
	}
}

template<>
void WsFileServer<ws_request, ws_response, ws_msg, ws_connection_hdl>::send_bin_msg(ws_connection_hdl& con_, const uint8_t* data, uint32_t len) const
{
    //auto con_ = (connection_hdl*)con;
    auto svr = ((asio_server*)m_endpoint);
    svr->send(con_, data, len, websocketpp::frame::opcode::binary);
    svr->interrupt(con_);
}

template<>
void WsFileServer<ws_request, ws_response, ws_msg, ws_connection_hdl>::send_http_msg(ws_connection_hdl& con_, bool not_found, const ws_response& rsp)  const
{
    asio_server::connection_ptr con = ((asio_server*)m_endpoint)->get_con_from_hdl(con_);
    if (not_found)
        con->set_status(websocketpp::http::status_code::not_found);
    else{
        con->set_status(rsp.get_status_code());
        con->set_body(rsp.get_body());
    }
    //con->defer_http_response();
    //con->send_http_response();
}

template<>
const ws_request& WsFileServer<ws_request, ws_response, ws_msg, ws_connection_hdl>::get_request(ws_connection_hdl& conn_)
{
    asio_server::connection_ptr con = ((asio_server*)m_endpoint)->get_con_from_hdl(conn_);
    return con->get_request();
}

static const std::string _url_parse_path(const std::string& url)
{
    auto pos = url.find('?');
    if (pos != std::string::npos) {
        return url.substr(0, pos);
    }
    return url;
}

template<>
bool WsFileServer<ws_request, ws_response, ws_msg, ws_connection_hdl>::handle_get_file(const std::string& url, ws_connection_hdl& con, const Request<ws_request>& req, Response<ws_response>& rsp)
{
    std::string type_;
    std::string body_;
    const std::string path = _url_parse_path(url);
    // 读取到文件内容、及ContentType
    if (read_file_content(path, type_, body_)) {
        // 设置响应内容
        rsp.set_body(body_);
        // 设置ContentType
        if (type_.length() != 0) {
            rsp.set_header("Content-Type", type_);
        }
        // 206 or 200
        if (req.get_header("Range").length() == 0) {
            rsp.set_status(websocketpp::http::status_code::ok);
        } else {
            rsp.set_status(websocketpp::http::status_code::partial_content);
        }
        return true;
    }

    return false;
}

template<>
bool WsFileServer<ws_request, ws_response, ws_msg, ws_connection_hdl>::init()
{
    try{
        m_endpoint = (void*)(new asio_server);

        // Initialize Asio
        ((asio_server*)m_endpoint)->init_asio();

        ((asio_server*)m_endpoint)->set_error_channels(websocketpp::log::elevel::all);
        ((asio_server*)m_endpoint)->set_access_channels(websocketpp::log::alevel::all);
        //((asio_server*)m_endpoint)->set_access_channels(websocketpp::log::alevel::all ^ websocketpp::log::alevel::frame_payload);

        ((asio_server*)m_endpoint)->set_open_handler(
            websocketpp::lib::bind(
                &WsFileServer<ws_request, ws_response, ws_msg, ws_connection_hdl>::_on_open, this, std::placeholders::_1));

        ((asio_server*)m_endpoint)->set_close_handler(
            websocketpp::lib::bind(
                &WsFileServer<ws_request, ws_response, ws_msg, ws_connection_hdl>::_on_close, this, std::placeholders::_1));

        ((asio_server*)m_endpoint)->set_message_handler(
            websocketpp::lib::bind(
                &WsFileServer<ws_request, ws_response, ws_msg, ws_connection_hdl>::_on_message, this, std::placeholders::_1, std::placeholders::_2));

        ((asio_server*)m_endpoint)->set_http_handler(
            websocketpp::lib::bind(
                &(WsFileServer<ws_request, ws_response, ws_msg, ws_connection_hdl>::_on_http), this, std::placeholders::_1));
            
    } catch(...) {
        return false;
    }
    return true;
}

template<>
void WsFileServer<ws_request, ws_response, ws_msg, ws_connection_hdl>::run(uint32_t port)
{
    ((asio_server*)m_endpoint)->set_reuse_addr(true);
    ((asio_server*)m_endpoint)->listen(port);
    ((asio_server*)m_endpoint)->start_accept();
    ((asio_server*)m_endpoint)->run();
}
